package nz.ac.massey.cs.webtech.rest;

import com.mongodb.*;
import com.mongodb.util.JSON;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.UnknownHostException;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import static nz.ac.massey.cs.webtech.rest.JSONUtils.*;
import org.bson.types.ObjectId;
import org.json.JSONObject;

/**
 * CRUD end point for BackBone - based clients.
 * Persistency is provided by MongoDB.
 * @author jens dietrich
 */
public class CRUDEndpoint extends HttpServlet {
    public static final String DB = "mydb";

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("application/json");
        // System.out.println("get " + request.getRequestURI());
        String type = decodeObjectType(request);
        JSONObject json;
        try {
            String oid = decodeObjectId(request);
            DB db = getDB();
            DBCollection coll = db.getCollection(type);
            if (oid!=null) {
                // fecthing one object by id
                System.out.println("fetching one object " + oid);
                DBObject result = coll.findOne(buildQueryById(oid));
                JSONObject saved = parseJSON(JSON.serialize(result));
                convertIdToExternal(saved);
                PrintWriter out = response.getWriter();
                out.print(saved.toString());
                out.close();
            }
            else {
                System.out.println("fetching all objects  of type " + type);
                DBObject query = buildQueryFromRequest(request);
                DBCursor result = coll.find(query);
                PrintWriter out = response.getWriter();
                out.println('[');
                while (result.hasNext()) {
                    if (result.numSeen()>0) out.print(',');
                    JSONObject saved = parseJSON(JSON.serialize(result.next()));
                    convertIdToExternal(saved);
                    out.print(saved.toString());
                    System.out.println(" adding result " + saved);
                }
                out.println(']');
                out.close();
            }

        } catch (Exception x) {
            this.getServletContext().log("Error updating object in DB",x);
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }
    
    @Override
    public void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("application/json");
        // System.out.println("delete " + request.getRequestURI());
        String type = decodeObjectType(request);
        try {
            String oid = decodeObjectId(request);
            // System.out.println("delete " + oid);
            DB db = getDB();
            DBCollection coll = db.getCollection(type);
            coll.findAndRemove(buildQueryById(oid));
            response.setStatus(HttpServletResponse.SC_OK);
        } catch (Exception x) {
            this.getServletContext().log("Error deleting object from DB",x);
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }
    
    /**
     * Put - update an existing objects.
     */
    @Override
    public void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("application/json");
        // System.out.println("post " + request.getRequestURI());
        String type = decodeObjectType(request);
        JSONObject json;
        try {
            json = parseJSONPayload(request);
            String oid = decodeObjectId(request);
            // System.out.println("update " + oid);
            DB db = getDB();
            DBCollection coll = db.getCollection(type);
            BasicDBObject doc = convertJSON2Internal(json);
            coll.findAndModify(buildQueryById(oid), doc);
            response.setStatus(HttpServletResponse.SC_OK);
        } catch (Exception x) {
            this.getServletContext().log("Error updating object in DB",x);
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
        // TODO: the object is not returned to the client - this would be useful 
        // if the object was changed concurrently (on the db) by another client
    }

    /**
     * Post - insert new objects, and send back the newly inserted object (with an id assigned by the db)
     */
    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        
        response.setContentType("application/json");
        System.out.println("post" + request.getRequestURI());
        String type = decodeObjectType(request);
        try {
            JSONObject json = parseJSONPayload(request);
            // System.out.println("request is " + json);
            DB db = getDB();
            DBCollection coll = db.getCollection(type);
            BasicDBObject doc = convertJSON2Internal(json);
            WriteResult result = coll.insert(doc);
            if (result.getError()!=null) {
                this.getServletContext().log("Error storing object in DB\n"+result.getError());
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            }
            else {
                PrintWriter out = response.getWriter();
                JSONObject saved = parseJSON(JSON.serialize(doc));
                convertIdToExternal(saved);
                out.print(saved.toString());
                out.close();
            }
            // System.out.println("object saved, id is " + doc.get("_id"));
        }
        catch (Exception x) {
           this.getServletContext().log("Error inserting object in DB",x);
           response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); 
        }
    }


    @Override
    public String getServletInfo() {
        return "CRUD endpoint for BackBone-based client applications";
    }
    
    /**
     * The JSONObjects sent by BackBone do not contain id information. 
     * This is solved by making the id part of the URL (last token in url attribute in models)
     * In this method, this part of the URL is extracted.
     * Note that by having access to the URL, we can write dynamic endpoints that work for arbitrary model types.
     */
    private String decodeObjectType(HttpServletRequest request) {
        String type = request.getPathInfo();
        // trim leading and trailing '/' characters
        if (type.startsWith("/")) type=type.substring(1);
        if (type.endsWith("/")) type=type.substring(0,type.length()-1);
        int x = type.indexOf('/');
        if (x>-1) {
            // has id, e.g. path info is /Student/42
            type = type.substring(0,x);
        }
        return type;
    }
    /**
     * Decode the object id from the pathinfo, e.g. if the path info is /Student/42
     * the object id is 42.
     */
    private String decodeObjectId(HttpServletRequest request) {
        String id = request.getPathInfo();
        // trim leading and trailing '/' characters
        if (id.startsWith("/")) id=id.substring(1);
        if (id.endsWith("/")) id=id.substring(0,id.length()-1);
        int x = id.indexOf('/');
        if (x>-1) {
            id = id.substring(x+1);
        }
        else {
            id=null;
        }
        return id;
    }
    
    private DB getDB() throws UnknownHostException {
        MongoClient mongoClient = new MongoClient();
        return mongoClient.getDB(DB);
    }
    
    private BasicDBObject buildQueryById(String oid) {
        return new BasicDBObject("_id", new ObjectId(oid));
    }

    /**
     * Build a db query from (get) request parameters. 
     * @param request
     * @return 
     */
    private DBObject buildQueryFromRequest(HttpServletRequest request) {
        DBObject query = new BasicDBObject();
        Enumeration<String> names = request.getAttributeNames();
        while (names.hasMoreElements()) {
            String name = names.nextElement();
            String value = request.getParameter(name);
            query.put(name,value);
        }
        return query;
    }
}
